/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2021. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * Description:
 * Author: huawei
 * Create: 2019-10-15
 */

#include <linux/io.h>
#include "devdrv_util.h"
#include "devdrv_pcie_link_info.h"

static u32 g_pcie_channel_status = DEVDRV_PCIE_COMMON_CHANNEL_ERR;

void devdrv_set_pcie_channel_status(u32 status)
{
#ifdef CFG_FEATURE_PCIE_LINK_INFO
    g_pcie_channel_status = status;
#endif
    return;
}

EXPORT_SYMBOL(devdrv_set_pcie_channel_status);

static int devdrv_get_pcie_mac_link_info(struct devdrv_pcie_link_info_para *pcie_link_info)
{
    u32 mac_reg_link_value;
    u32 ltssm_st;
    void __iomem *apb_base = NULL;

    if (pcie_link_info == NULL) {
        devdrv_err("pcie_link_info is NULL.\n");
        return -EINVAL;
    }
    apb_base = ioremap(PCIE_BASE_ADDR, PCIE_REG_SIZE);
    if (apb_base == NULL) {
        devdrv_err("ioremap fail, apb_base is NULL.\n");
        return -ENOMEM;
    }

    mac_reg_link_value = readl(apb_base + PCIE_MAC_REG_BASE + PCIE_MAC_REG_LINK_ADDR);
    devdrv_info("read mac reg link. (reg_value=0x%x)\n", mac_reg_link_value);

    // link_status
    pcie_link_info->link_status = DEVDRV_PCIE_LINK_STATUS_DOWN;
    ltssm_st = (mac_reg_link_value >> PCIE_MAC_REG_LINK_LTSSM_ST_OFFSET) & 0x3F;
    if (ltssm_st == PCIE_MAC_REG_LINK_LTSSM_L0) {
        pcie_link_info->link_status = DEVDRV_PCIE_LINK_STATUS_OK;
    } else {
        iounmap(apb_base);
        apb_base = NULL;
        return 0;
    }

    // rate_mode
    pcie_link_info->rate_mode = (mac_reg_link_value >> PCIE_MAC_REG_LINK_SPEED_OFFSET) & 0xF;
    // lane_num
    pcie_link_info->lane_num = mac_reg_link_value & 0x3F;

    iounmap(apb_base);
    apb_base = NULL;
    return 0;
}

int devdrv_get_pcie_link_info(u32 dev_id, struct devdrv_pcie_link_info_para* pcie_link_info)
{
    int ret;

    if (pcie_link_info == NULL) {
        devdrv_err("pcie_link_info is NULL.\n");
        return -EINVAL;
    }
    ret = devdrv_get_pcie_mac_link_info(pcie_link_info);
    if (ret != 0) {
        devdrv_err("get mac link info err. (ret=%d, dev_id=%u)\n", ret, dev_id);
        return ret;
    }

    // if pcie link up status is down, no need to check channel status
    if (pcie_link_info->link_status == DEVDRV_PCIE_LINK_STATUS_DOWN) {
        return 0;
    }

    if (g_pcie_channel_status != DEVDRV_PCIE_COMMON_CHANNEL_OK) {
        pcie_link_info->link_status = DEVDRV_PCIE_LINK_STATUS_CHANNEL_ERR;
    }

    return 0;
}

EXPORT_SYMBOL(devdrv_get_pcie_link_info);